package top.codecrab.eblog.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.servlet.ServletUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import top.codecrab.eblog.common.response.CommentCount;
import top.codecrab.eblog.common.response.R;
import top.codecrab.eblog.config.Constants;
import top.codecrab.eblog.dto.IpRegion;
import top.codecrab.eblog.entity.Comment;
import top.codecrab.eblog.entity.Post;
import top.codecrab.eblog.entity.User;
import top.codecrab.eblog.entity.UserMessage;
import top.codecrab.eblog.mapper.CommentMapper;
import top.codecrab.eblog.mapper.UserMapper;
import top.codecrab.eblog.mapper.UserMessageMapper;
import top.codecrab.eblog.service.CommentService;
import top.codecrab.eblog.service.PostService;
import top.codecrab.eblog.service.WebSocketService;
import top.codecrab.eblog.shiro.AccountProfile;
import top.codecrab.eblog.utils.IpRegionUtil;
import top.codecrab.eblog.utils.ShiroUtil;
import top.codecrab.eblog.vo.CommentVo;
import top.codecrab.eblog.vo.UserVo;

import javax.servlet.http.HttpServletRequest;
import java.util.Collections;
import java.util.List;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author codecrab
 * @since 2021-02-26
 */
@Service
@RequiredArgsConstructor
public class CommentServiceImpl extends ServiceImpl<CommentMapper, Comment> implements CommentService {

    private final UserMapper userMapper;

    private final PostService postService;

    private final UserMessageMapper messageMapper;

    private final WebSocketService socketService;

    private final HttpServletRequest request;

    @Override
    public IPage<CommentVo> paging(Page<Comment> page, Long postId, Long userId, String order) {
        return baseMapper.selectComments(page, new QueryWrapper<Comment>()
            .eq(postId != null, "post_id", postId)
            .eq(userId != null, "user_id", userId)
            .eq("c.is_deleted", false)
            .ge("c.status", 0)
            .orderByDesc("level", "vote_up", order)
        );
    }

    @Override
    public List<CommentVo> commentsAndPostTitle(QueryWrapper<Comment> wrapper) {
        return baseMapper.selectCommentsAndPostTitle(wrapper);
    }

    @Override
    public void updateStatus(QueryWrapper<Comment> wrapper, int status) {
        baseMapper.updateStatus(status, wrapper);
    }

    @Override
    public List<UserVo> getTopCommentUser() {
        DateTime beginOfWeek = DateUtil.beginOfWeek(DateTime.now());
        // 查询出本周的发生过评论的用户
        List<Long> userIds = baseMapper.selectTopCommentUserId(beginOfWeek);

        if (CollUtil.isEmpty(userIds)) {
            return Collections.emptyList();
        }

        // 查询这些用户本周的评论
        return userMapper.selectTopUserWithComments(Page.of(1, 3, false), userIds, beginOfWeek);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public R postReply(Long postId, String content) {
        Assert.notNull(postId, "文章不存在或已被删除");
        Assert.notBlank(content, "评论内容不能为空");

        Post post = postService.getById(postId);
        Assert.notNull(post, "文章不存在或已被删除");

        if (post.getStatus() == 0) {
            return R.fail("文章未通过审核无法评论");
        }

        AccountProfile profile = ShiroUtil.getProfile();
        Assert.notNull(profile, "用户登录状态已失效，请退出重新登录");

        Long profileId = profile.getId();
        Assert.notNull(profileId, "用户登录状态已失效，请退出重新登录");

        //新建评论
        Comment comment = saveComment(postId, content);

        //更新博客评论数
        Post newPost = new Post();
        newPost.setId(post.getId());
        newPost.setCommentCount(post.getCommentCount() + 1);
        post.setCommentCount(newPost.getCommentCount());
        postService.updateById(newPost);

        //更新本周热议
        updateCurrWeekHot(postId, post);

        Long commentId = comment.getId();
        //评论的文章所属用户不等于当前用户，再提醒
        if (!post.getUserId().equals(profileId)) {
            sendMessageNotice(postId, content, commentId, post.getUserId(), 1);
            return R.ok().action("/post/" + postId);
        }

        //如果是回复，进行提醒
        if (content.startsWith(Constants.AI_TE)) {
            //被回复的用户
            String username;
            try {
                username = content.substring(1, content.indexOf(" "));
            } catch (Exception exception) {
                throw new RuntimeException("回复评论的格式有误");
            }

            Assert.notBlank(username, "回复的用户名不能为空");

            User user = userMapper.selectOne(
                new QueryWrapper<User>()
                    .eq("username", username)
            );

            Assert.notNull(user, "用户已注销或不存在");

            sendMessageNotice(postId, content, commentId, user.getId(), 2);
        }
        return R.ok().action("/post/" + postId);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public R removeComment(Long id) {
        Comment comment = this.getById(id);
        Assert.notNull(comment, "找不到该评论或已被删除");

        // 文章id
        Long postId = comment.getPostId();
        // 当前用户id
        Long profileId = ShiroUtil.getProfileId();
        // 评论ID
        Long commentId = comment.getId();

        //表示删除自己的评论
        if (comment.getUserId().equals(profileId)) {
            Post post = postService.getById(postId);
            post.setCommentCount(post.getCommentCount() - 1);
            this.removeComment(commentId, postId, post);
            return R.ok();
        }

        Post post = postService.getOne(
            new QueryWrapper<Post>()
                .eq("id", postId)
                .eq("user_id", profileId)
        );

        //表示删除自己文章下的评论
        if (post != null) {
            post.setCommentCount(post.getCommentCount() - 1);
            this.removeComment(commentId, postId, post);
            return R.ok();
        }

        return R.fail("您只能删除自己或自己文章下的评论");
    }

    private void updateCurrWeekHot(Long postId, Post post) {
        CommentCount count = new CommentCount();
        count.setPostId(postId);
        count.setPostTitle(post.getTitle());
        count.setPostViewCount(post.getViewCount());
        count.setPostCommentCount(post.getCommentCount());
        //更新缓存的分数，由于已经是更新好的值，存在直接覆盖就好，不存在添加
        postService.packageWeekHot(count, postId);
    }

    private void sendMessageNotice(Long postId, String content, Long commentId, Long userId, int type) {
        Long profileId = ShiroUtil.getProfileId();

        UserMessage message = new UserMessage();
        message.setFromUserId(profileId);
        message.setToUserId(userId);
        message.setPostId(postId);
        message.setCommentId(commentId);
        message.setContent(content);
        message.setType(type);
        //0:未读
        message.setStatus(0);
        messageMapper.insert(message);

        //即时通知作者或被@的人
        socketService.sendNotReadCountToUser(message.getToUserId());
    }

    private Comment saveComment(Long postId, String content) {
        Long profileId = ShiroUtil.getProfileId();

        Comment comment = new Comment();
        comment.setContent(content);
        comment.setParentId(0L);
        comment.setPostId(postId);
        comment.setUserId(profileId);
        comment.setVoteUp(0);
        comment.setVoteDown(0);
        comment.setLevel(0);
        //0:未审核
        comment.setStatus(1);

        IpRegion ipRegion = IpRegionUtil.searchRegionByXdb(ServletUtil.getClientIP(request));
        if (ipRegion != null) {
            comment.setIpRegion(StrUtil.blankToDefault(ipRegion.getCity(), ipRegion.getProvince()));
        }

        this.save(comment);
        return comment;
    }

    private void removeComment(Long commentId, Long postId, Post post) {
        post.setCommentCount(Math.max(post.getCommentCount() - 1, 0));
        postService.updateById(post);

        this.removeById(commentId);

        //本周热议-1
        updateCurrWeekHot(postId, post);
    }

}
